
package nachos.network;

import nachos.machine.Lib;
import nachos.threads.*;
import nachos.vm.VMKernel;
import nachos.userprog.*;

import java.util.*;

public class ReadThread extends KThread implements TimerListener {
	
	private ReadRunner runner = null;

   public ReadThread(int pid) {
	   super();
	   runner = new ReadRunner( this );
	   setTarget( runner );
	   this.pid = pid;
   }
   

   public void read( int port, int fd) {
      System.out.println( "=== Read Thread, added port " + port + "  ===" );
      PortConn tempPort = new PortConn(port, fd);
      // add port to ports linkedList. Make sure this port for this connection is not
      // already in the list
      if (!ports.contains(tempPort))
      {
    	  ports.add(tempPort);
      }
   }

   /*public void run() {
	   while( shouldRun ) {
		   // Check if any connections are established
		 if( ports.size() > 0 ) {
			 //System.out.println( ports.size() );
			 
			 thisPort = ports.remove();
			 NetProcess thisProcess = (NetProcess)UserProcess.processes.get(pid);
			 // Check if receiveBuffer contents need to be shifted (because
			 // read syscall has been performed and data was removed from
			 // the buffer)
			 int moveNum = thisProcess.connectionArray[thisPort.getFd()].moveCount;
			 if (moveNum > 0)
			 {
				 // Shift rcv buffer contents by moveCount
				 for (int a = moveNum; a < Connection.rcvBufferLength; a++)
				 {
					 thisProcess.connectionArray[thisPort.getFd()].receiveBuffer[a-moveNum] = thisProcess.connectionArray[thisPort.getFd()].receiveBuffer[a];
				 }
				 // Reset moveCount to 0
				 thisProcess.connectionArray[thisPort.getFd()].moveCount = 0;
			 }
			 
			 // if the port for that connection has messages on it
			 if (postOffice.queues[thisPort.getPort()].getSize() > 0) {
				 // Get message from port
			     MailMessage msg = NetKernel.getPostOffice().receive( thisPort.getPort() );
			     System.out.println( "=== RECEIVED ===" );
			     System.out.println( "MailMessage: " + msg );
			     
			     // Put the message on the receiveBuffer of the appropriate connection
			     assignMessage(msg, thisPort);
			 }
			 // add the port to the end of the list
			 if (!ports.contains(thisPort))
			 {
				 ports.add(thisPort);
			 }
		 }
	   }
   }*/
   
   public void stop()
   {
	   runner.stop();
   }
   
   protected void assignMessage(MailMessage msg, PortConn thatPort)
   {
       /*
	   // Check the state of connection where message is going. If state is established, try
	   // to deliver it.  If state is STP_RCVD, make sure sequence number is less than stop message
	   // sequence number and then deliver it.  If it is in any other state, drop the message.
	   NetProcess thisProcess = (NetProcess)UserProcess.processes.get(pid);
	   
	   // Get sequence number from current message.
	   byte[] tempConts2 = new byte[4];
	   tempConts2[3] = msg.contents[5];
	   tempConts2[2] = msg.contents[4];
	   tempConts2[1] = msg.contents[3];
	   tempConts2[0] = msg.contents[2];
	   int seqNumCurr = Connection.byteArrayToInt(tempConts2);
	   
	   // if the received message is a stop message, set the stop message seq num and connection state
	   if ((msg.contents[1] & 4) == 4)
	   {
		   thisProcess.connectionArray[thatPort.getFd()].stpMsgSeqNum = seqNumCurr;
		   thisProcess.connectionArray[thatPort.getFd()].state = Connection.stpRcvd;
	   }
	   
	   // if the msg is an ack, don't deliver it. Confirm the ack by taking it off the connection's ackList
	   if ((msg.contents[1] & 2) == 2)
	   {
		   // Take ack off ackList
           //TODO: is this really fixed?
		   thisProcess.connectionArray[thatPort.getFd()].ackList.remove(Connection.getSeqNumAsInt(msg));
		   Lib.debug(dbgNick, "Removing waiting ack: " + Connection.getSeqNumAsInt(msg));
		   return;
	   }
	   
	   if (thisProcess.connectionArray[thatPort.getFd()].state == Connection.established)
	   {
		   deliverMsg(thatPort, seqNumCurr, thisProcess, msg);
	   }
	   else if (thisProcess.connectionArray[thatPort.getFd()].state == Connection.stpRcvd)
	   {
		   // Check to make sure that sequence number is less than stop message sequence number.
		   // If yes, deliver.  If no, discard.
		   if (seqNumCurr <= thisProcess.connectionArray[thatPort.getFd()].stpMsgSeqNum)
		   {
			   deliverMsg(thatPort, seqNumCurr, thisProcess, msg);
		   }
		   else
		   {
			   Lib.debug(dbgNick, "This message came after the stop message");
		   }
	   }
        */
   }
   
   private void deliverMsg(PortConn thatPort, int seqNumCurr, NetProcess thisProcess, MailMessage msg)
   {
       /*
	   int spot = -1;
	   // Deliver message
	   // Traverse array back to front to find first non-null entry
	   for (int i = (Connection.rcvBufferLength-1); i >= 0; i--)
	   {
		   if (!(thisProcess.connectionArray[thatPort.getFd()].receiveBuffer[i] == null))
		   {
			   // Get sequence number from message in receive buffer.
			   byte[] tempConts = new byte[4];
			   tempConts[3] = thisProcess.connectionArray[thatPort.getFd()].receiveBuffer[i].contents[5];
			   tempConts[2] = thisProcess.connectionArray[thatPort.getFd()].receiveBuffer[i].contents[4];
			   tempConts[1] = thisProcess.connectionArray[thatPort.getFd()].receiveBuffer[i].contents[3];
			   tempConts[0] = thisProcess.connectionArray[thatPort.getFd()].receiveBuffer[i].contents[2];
			   int seqNum = Connection.byteArrayToInt(tempConts);
			   
			   // Subtract message sequence number from current message seq num and
			   // add result to i.
			   int offset = i + (seqNumCurr - seqNum);
			   
			   // Check that offset >=0 and offset < 16.  If so, set spot = offset.
			   if ((offset >= 0) && (offset < Connection.rcvBufferLength))
			   {
				   spot = offset;
			   }
			   else
			   {
				   Lib.debug(dbgNick, "Spot not in buffer.  Msg Dropped");
			   }
			   // Either spot in buffer has been found or message should be dropped
			   break;
		   }
		   // If all all spots in buffer are null
		   if (i == 0)
		   {
			   // Check if firstRcvBuffSeqNum == -1
			   if (thisProcess.connectionArray[thatPort.getFd()].firstRcvBuffSeqNum == -1)
			   {
				   // give msg first spot in rcv buffer
				   spot = 0;
			   }
			   else
			   {
				   // Subtract firstRcvBuffSeqNum from current msg seq num to get proper
				   // offset from beginning of rcv buffer
				   spot = seqNumCurr - thisProcess.connectionArray[thatPort.getFd()].firstRcvBuffSeqNum;
			   }
		   }
	   }
	   // Check that spot found in receiveBuffer
	   if (!((spot < 0) || (spot >= Connection.rcvBufferLength)))
	   {
		   thisProcess.connectionArray[thatPort.getFd()].receiveBuffer[spot] = msg;
		   System.out.println("Message placed in spot " + spot + " of receiveBuffer");
		   
		   // Send ack message back to sender if rcvd msg was not an ack or stop
		   if (!(((msg.contents[1] & 2) == 2) || ((msg.contents[1] & 4) == 4)))
		   {
			   // Make acknowledgement message
			   MailMessage ackMess = thisProcess.connectionArray[thatPort.getFd()].makeAck();
			   
			   // add ack message to sendThread
			   Connection.sendThread.add( ackMess );
		   }
	   }
	   else
	   {
		   Lib.debug(dbgNick, "Spot not in buffer.  Msg Dropped");
	   }
        */
   }

   public void handleTimer( int tick ) {
      //System.out.println( "ReadThread got tick: " + tick );
   }
   
   private boolean shouldRun = true;
   
   protected LinkedList<PortConn> ports = new LinkedList<PortConn>();
   PortConn thisPort;
   
   protected static PostOffice postOffice = NetKernel.getPostOffice();
   
   protected int pid;
   
   public static final char dbgNick = 's';

}

class PortConn
{
	public PortConn(int port, int fd)
	{
		this.port = port;
		this.fd = fd;
	}
	
	public int getPort()
	{
		return port;
	}
	
	public int getFd()
	{
		return fd;
	}
	
	private int port, fd;
}

class ReadRunner implements Runnable {
	   private boolean shouldRun = true;
	   public ReadThread readThread = null;
	   public ReadRunner( ReadThread readThread ) {
	      this.readThread = readThread;
	   }
	   
	   public void stop() {
	      shouldRun = false;
	   }
	   
	   public void run() {
	      /*while( shouldRun ) {
	    	  if( readThread.ports.size() > 0 ) {
	 			 //System.out.println( ports.size() );
	 			 
	    		  readThread.thisPort = readThread.ports.remove();
	 			 NetProcess thisProcess = (NetProcess)UserProcess.processes.get(readThread.pid);
	 			 // Check if receiveBuffer contents need to be shifted (because
	 			 // read syscall has been performed and data was removed from
	 			 // the buffer)
             try {
                int moveNum = thisProcess.connectionArray[readThread.thisPort.getFd()].moveCount;
                if (moveNum > 0)
                {
                   // Shift rcv buffer contents by moveCount
                	MailMessage[] tempBuff = new MailMessage[thisProcess.connectionArray[readThread.thisPort.getFd()].receiveBuffer.length];
                	System.arraycopy(thisProcess.connectionArray[readThread.thisPort.getFd()].receiveBuffer, 0, tempBuff, 0, tempBuff.length);
                   for (int a = moveNum; a < Connection.rcvBufferLength; a++)
                   {
                	   tempBuff[a-moveNum] = tempBuff[a];
                   }
                	// Reset moveCount to 0
                    thisProcess.connectionArray[readThread.thisPort.getFd()].moveCount = 0;
                   System.arraycopy(tempBuff, 0, thisProcess.connectionArray[readThread.thisPort.getFd()].receiveBuffer, 0, tempBuff.length);
                }

                // if the port for that connection has messages on it
                if (ReadThread.postOffice.queues[readThread.thisPort.getPort()].getSize() > 0) {
                   // Get message from port
                   MailMessage msg = NetKernel.getPostOffice().receive( readThread.thisPort.getPort() );
                   System.out.println( "=== RECEIVED ===" );
                   System.out.println( "MailMessage: " + msg );

                   // Put the message on the receiveBuffer of the appropriate connection
                   readThread.assignMessage(msg, readThread.thisPort);
                }
                // add the port to the end of the list
                if (!readThread.ports.contains(readThread.thisPort))
                {
                   readThread.ports.add(readThread.thisPort);
                }
             } catch ( Exception e ) {
                System.err.println( "ReadThread got an Exception in run(): " + e );
             }
	 		 }
	    	  KThread.yield();
	      }*/
	   }
	}
